/*
    使用条件变量解决生产者、计算者、消费者问题
    
    + 系统中有3个线程：生产者、计算者、消费者
    + 系统中有2个容量为4的缓冲区：buffer1、buffer2
    + 生产者
    - 生产'a'、'b'、'c'、‘d'、'e'、'f'、'g'、'h'八个字符
    - 放入到buffer1
    + 计算者
    - 从buffer1取出字符
    - 将小写字符转换为大写字符
    - 放入到buffer2
    + 消费者
    - 从buffer2取出字符，将其打印到屏幕上
    + 程序输出结果(实际输出结果是交织的)
    a
    b
    c
    ...
        a:A
        b:B
        c:C
        ...
            A
            B
            C
            ...
*/

/*
    互斥关系：bufffer1 and bufffer2，2个：mutex1, mutex2
    同步关系：生产者->计算者，计算者->消费者，4个：wait_empty_buffer1, wait_full_buffer1, wait_empty_buffer2, wait_full_buffer2
*/

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define BUFFER_SIZE 4
#define ITEM_COUNT (BUFFER_SIZE * 2)

char buffer1[BUFFER_SIZE];
int in1, out1;

char buffer2[BUFFER_SIZE];
int in2, out2;

pthread_mutex_t mutex1;
pthread_cond_t wait_empty_buffer1, wait_full_buffer1;

pthread_mutex_t mutex2;
pthread_cond_t wait_empty_buffer2, wait_full_buffer2;

int buffer_is_empty(int *in, int *out) {
    return *in == *out;
}

int buffer_is_full(int *in, int *out) {
    return (*in + 1) % BUFFER_SIZE == *out;
}

char get_item(char buffer[], int *out) {
    char item;
    item = buffer[*out];
    *out = (*out + 1) % BUFFER_SIZE;
    return item;
}

void put_item(char buffer[], int *in, char item) {
    buffer[*in] = item;
    *in = (*in + 1) % BUFFER_SIZE;
}

void *consume(void *arg) {
    int i;
    char item;
    for(i = 0; i < ITEM_COUNT; i ++) {
        pthread_mutex_lock(&mutex2);
        while(buffer_is_empty(&in2, &out2))
            pthread_cond_wait(&wait_full_buffer2, &mutex2);
        
        item = get_item(buffer2, &out2);
        printf("        %c\n", item);
        pthread_cond_signal(&wait_empty_buffer2);
        pthread_mutex_unlock(&mutex2);
        
    }
    return NULL;
}

void *compute(void *arg) {
    int i;
    char item;
    for(i = 0; i < ITEM_COUNT; i ++) {
        pthread_mutex_lock(&mutex1);
        while(buffer_is_empty(&in1, &out1))
            pthread_cond_wait(&wait_full_buffer1, &mutex1);
        item = get_item(buffer1, &out1);
        pthread_cond_signal(&wait_empty_buffer1);
        pthread_mutex_unlock(&mutex1);

        pthread_mutex_lock(&mutex2);
        while(buffer_is_full(&in2, &out2))
            pthread_cond_wait(&wait_empty_buffer2, &mutex2);
        put_item(buffer2, &in2, item - 32);
        printf("    %c:%c\n", item, item - 32);
        pthread_cond_signal(&wait_full_buffer2);
        pthread_mutex_unlock(&mutex2);
    }
    return NULL;
}

void *produce(void *arg) {
    int i;
    char item;
    for(i = 0; i < ITEM_COUNT; i ++) {
        pthread_mutex_lock(&mutex1);
        while(buffer_is_full(&in1, &out1))
            pthread_cond_wait(&wait_empty_buffer1, &mutex1);
        
        item = 'a' + i;
        put_item(buffer1, &in1, item);
        printf("%c\n", item);
        pthread_cond_signal(&wait_full_buffer1);
        pthread_mutex_unlock(&mutex1);
    }
    return NULL;
}

int main() {
    pthread_t computer_tid, consumer_tid;

    pthread_mutex_init(&mutex1, NULL);
    pthread_mutex_init(&mutex2, NULL);
    pthread_cond_init(&wait_empty_buffer1, NULL);
    pthread_cond_init(&wait_full_buffer1, NULL);    
    pthread_cond_init(&wait_empty_buffer2, NULL);
    pthread_cond_init(&wait_full_buffer2, NULL);
    
    
    pthread_create(&computer_tid, NULL, compute, NULL);
    pthread_create(&consumer_tid, NULL, consume, NULL);
    produce(NULL);
    
    pthread_join(consumer_tid, NULL);    

    return 0;
}